#!/usr/bin/env python3
#
# Copyright 2015, Corey Richardson
# Copyright 2014, NICTA
#
# This software may be distributed and modified according to the terms of
# the BSD 2-Clause license. Note that NO WARRANTY is provided.
# See "LICENSE_BSD2.txt" for details.
#
# @TAG(NICTA_BSD)
#

# seL4 Invocation ID Generator
# ============================

import argparse
import sys
from itertools import chain
import xml.dom.minidom
import pkg_resources
# We require jinja2 to be at least version 2.10 as we use the 'namespace' feature from
# that version
pkg_resources.require("jinja2>=2.10")
from jinja2 import Environment, BaseLoader

COMMON_HEADER = """
/* @LICENSE(NICTA) */

/* This header was generated by kernel/tools/invocation_header_gen.py.
 *
 * To add an invocation call number, edit libsel4/include/interfaces/sel4.xml.
 *
 */"""

INVOCATION_TEMPLATE = COMMON_HEADER + """
#[repr(C)]
pub enum InvocationLabel {
    InvalidInvocation = 0,
    {%- for label, condition in invocations %}
    {%- if condition %}
    #[cfg({{condition}})]
    {%- endif %}
    {{label}},
    {%- endfor %}
}
"""

def parse_args():
    parser = argparse.ArgumentParser(
        description='Generate seL4 invocation API \
        constants and header files')
    parser.add_argument('--dest', type=argparse.FileType('w'),
            help='Name of file to create', required=True)
    parser.add_argument('files', nargs='+', help='XML files to parse invocations from')

    return parser.parse_args()

def parse_xml(xml_file):
    try:
        doc = xml.dom.minidom.parse(xml_file)
    except:
        print >> sys.stderr, "Error: invalid xml file"
        sys.exit(-1)

    invocation_labels = []
    for method in doc.getElementsByTagName("method"):
        condition = method.getAttribute("condition")
        # HACK: ugly hacks to handle simple CPP expressions (very fragile)
        if condition == "(!defined CONFIG_KERNEL_MCS) && CONFIG_MAX_NUM_NODES > 1":
            # NB: CONFIG_MAX_NUM_NODES > 1 =>'s CONFIG_SMP_SUPPORT
            condition = 'all(not(feature = "CONFIG_KERNEL_MCS"), feature = "CONFIG_SMP_SUPPORT")'
        elif condition == "!defined(CONFIG_KERNEL_MCS) && CONFIG_MAX_NUM_NODES > 1":
            # NB: CONFIG_MAX_NUM_NODES > 1 =>'s CONFIG_SMP_SUPPORT
            condition = 'all(not(feature = "CONFIG_KERNEL_MCS"), feature = "CONFIG_SMP_SUPPORT")'
        elif condition == "CONFIG_MAX_NUM_NODES > 1":
            condition = 'feature = "CONFIG_SMP_SUPPORT"'
        elif condition:
            condition = condition.replace('defined', '')
            condition = condition.replace('(', '')
            condition = condition.replace(')', '')
            if 'CONFIG_' in condition:
                condition = 'feature = "' + condition + '"'
            if '!' in condition:
                condition = 'not(%s)' % condition.replace('!', '')

        invocation_labels.append((str(method.getAttribute("id")),
                                  str(condition)))

    return invocation_labels

def generate(args, invocations):
    template = Environment(loader=BaseLoader).from_string(INVOCATION_TEMPLATE)
    data = template.render({
        'header_title': "LIBRUSTSEL4",
        'invocations': invocations
    })
    args.dest.write(data)

if __name__ == "__main__":
    args = parse_args()

    invocations = chain.from_iterable(parse_xml(xml) for xml in args.files)

    generate(args, invocations)
    args.dest.close()
